home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-01 / ohlutil.zip / MV.C < prev    next >
C/C++ Source or Header  |  1990-06-22  |  11KB  |  479 lines

  1. /* mv -- move or rename files
  2.    Copyright (C) 1986, 1989, 1990 Free Software Foundation, Inc.
  3.  
  4.    This program is free software; you can redistribute it and/or modify
  5.    it under the terms of the GNU General Public License as published by
  6.    the Free Software Foundation; either version 1, or (at your option)
  7.    any later version.
  8.  
  9.    This program is distributed in the hope that it will be useful,
  10.    but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.    GNU General Public License for more details.
  13.  
  14.    You should have received a copy of the GNU General Public License
  15.    along with this program; if not, write to the Free Software
  16.    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
  17.  
  18. /* Options:
  19.    -i (+interactive)
  20.    Causes mv to require confirmation from the user before
  21.    performing any move that would destroy an existing file.
  22.  
  23.    -f (+force)
  24.    Causes mv to assume a 'y' answer to all questions it would normally ask
  25.    (and not ask the questions).
  26.  
  27.    If -f is not given, the user is queried when a move would
  28.    destroy an existing file whose mode prohibits writing, unless
  29.    stdin is not a tty, in which case the file is skipped.
  30.  
  31.    This test is slightly different from the test 4.2BSD mv makes; 4.2 mv
  32.    merely checks whether the user can write the file.  This actually checks
  33.    the write bits in the mode.  The difference shows when super-user tries
  34.    to mv onto a file with no write bits set.
  35.  
  36.    -v (+verbose)
  37.    List the name of each file as it is moved, and the name it is moved to.
  38.  
  39.    Written by Mike Parker and David MacKenzie */
  40.  
  41. #include <stdio.h>
  42. #include <errno.h>
  43. #include <sys/types.h>
  44. #include "system.h"
  45. #include "getopt.h"
  46. #include "backupfile.h"
  47.  
  48. #ifdef STDC_HEADERS
  49. #include <stdlib.h>
  50. #else
  51. char *getenv ();
  52.  
  53. extern int errno;
  54. #endif
  55.  
  56. enum backup_type get_version ();
  57. int copy ();
  58. int do_move ();
  59. int movefile ();
  60. int yesno ();
  61. void error ();
  62. void strip_trailing_slashes ();
  63. void usage ();
  64.  
  65. /* The name this program was run with. */
  66. char *program_name;
  67.  
  68. /* If nonzero, query the user before overwriting files. */
  69. int interactive;
  70.  
  71. /* If nonzero, override as much protection as possible. */
  72. int force;
  73.  
  74. /* If nonzero, list each file as it is moved. */
  75. int verbose;
  76.  
  77. /* If nonzero, stdin is not a tty. */
  78. int stdin_not_tty;
  79.  
  80. /* Return nonzero if FN is a directory or a symlink to a directory,
  81.    zero if not. */
  82. int
  83. isdir (fn)
  84.      char *fn;
  85. {
  86.   struct stat stats;
  87.  
  88.   return (stat (fn, &stats) == 0 && (stats.st_mode & S_IFMT) == S_IFDIR);
  89. }
  90.  
  91. struct option long_options[] =
  92. {
  93.   {"backup", 0, NULL, 'b'},
  94.   {"force", 0, &force, 1},
  95.   {"interactive", 0, &interactive, 1},
  96.   {"suffix", 1, NULL, 'S'},
  97.   {"verbose", 0, &verbose, 1},
  98.   {"version-control", 1, NULL, 'V'},
  99.   {NULL, 0, NULL, 0}
  100. };
  101.  
  102. void
  103. main (argc, argv)
  104.      int argc;
  105.      char **argv;
  106. {
  107.   int c;
  108.   int ind;
  109.   int errors;
  110.   int make_backups = 0;
  111.   char *version;
  112.  
  113.   version = getenv ("SIMPLE_BACKUP_SUFFIX");
  114.   if (version)
  115.     simple_backup_suffix = version;
  116.   version = getenv ("VERSION_CONTROL");
  117.   program_name = argv[0];
  118.   interactive = force = verbose = 0;
  119.   errors = 0;
  120.  
  121.   while ((c = getopt_long (argc, argv, "bfivS:V:", long_options, &ind))
  122.      != EOF)
  123.     {
  124.       if (c == 0 && long_options[ind].flag == NULL)
  125.     c = long_options[ind].val;
  126.       switch (c)
  127.     {
  128.     case 0:
  129.       break;
  130.     case 'b':
  131.       make_backups = 1;
  132.       break;
  133.     case 'f':
  134.       force = 1;
  135.       break;
  136.     case 'i':
  137.       interactive = 1;
  138.       break;
  139.     case 'v':
  140.       verbose = 1;
  141.       break;
  142.     case 'S':
  143.       simple_backup_suffix = optarg;
  144.       break;
  145.     case 'V':
  146.       version = optarg;
  147.       break;
  148.     default:
  149.       usage ();
  150.     }
  151.     }
  152.   if (argc < optind + 2)
  153.     usage ();
  154.  
  155.   if (make_backups)
  156.     backup_type = get_version (version);
  157.  
  158.   strip_trailing_slashes (argv[argc - 1]);
  159.  
  160.   if (argc > optind + 2 && !isdir (argv[argc - 1]))
  161.     error (1, 0, "when moving multiple files, last argument must be a directory");
  162.  
  163.   if (interactive)
  164.     force = 0;
  165.   stdin_not_tty = !isatty (0);
  166.  
  167.   /* Move each arg but the last onto the last. */
  168.   for (; optind < argc - 1; ++optind)
  169.     errors |= movefile (argv[optind], argv[argc - 1]);
  170.  
  171.   exit (errors);
  172. }
  173.  
  174. /* Move file FROM onto TO.  Handles the case when TO is a directory.
  175.    Return 0 if successful, 1 if an error occurred.  */
  176. int
  177. movefile (from, to)
  178.      char *from;
  179.      char *to;
  180. {
  181.   strip_trailing_slashes (from);
  182.   if (isdir (to))
  183.     {
  184.       /* Target is a directory; build full target filename. */
  185.       char *cp;
  186.       char *newto;
  187.  
  188.       cp = rindex (from, '/');
  189.       if (cp)
  190.     cp++;
  191.       else
  192.     cp = from;
  193.  
  194.       newto = (char *) alloca (strlen (to) + 1 + strlen (cp) + 1);
  195.       sprintf (newto, "%s/%s", to, cp);
  196.       return do_move (from, newto);
  197.     }
  198.   else
  199.     return do_move (from, to);
  200. }
  201.  
  202. struct stat to_stats, from_stats;
  203.  
  204. /* Move FROM onto TO.  Handles cross-filesystem moves.
  205.    If TO is a directory, FROM must be also.
  206.    Return 0 if successful, 1 if an error occurred.  */
  207. int
  208. do_move (from, to)
  209.      char *from;
  210.      char *to;
  211. {
  212.   char *to_backup = NULL;
  213.  
  214.   if (lstat (from, &from_stats) != 0)
  215.     {
  216.       error (0, errno, "%s", from);
  217.       return 1;
  218.     }
  219.  
  220.   if (verbose)
  221.     printf ("  %s -> %s\n", from, to);
  222.  
  223.   if (lstat (to, &to_stats) == 0)
  224.     {
  225.       if (from_stats.st_dev == to_stats.st_dev
  226.       && from_stats.st_ino == to_stats.st_ino)
  227.     {
  228.       error (0, 0, "`%s' and `%s' are the same file", from, to);
  229.       return 1;
  230.     }
  231.  
  232.       if ((to_stats.st_mode & S_IFMT) == S_IFDIR)
  233.     {
  234.       error (0, 0, "%s: cannot overwrite directory", to);
  235.       return 1;
  236.     }
  237.       if (interactive)
  238.     {
  239.       fprintf (stderr, "%s: replace `%s'? ", program_name, to);
  240.       if (!yesno ())
  241.         return 0;
  242.     }
  243.       else if (!force)
  244.     {
  245.       int may_overwrite;
  246.  
  247.       /* Treat the file as nonwritable if it lacks write permission bits,
  248.          even if we are root.  */
  249. #ifdef S_IFLNK
  250.       if ((to_stats.st_mode & S_IFMT) == S_IFLNK)
  251.         may_overwrite = 1;
  252.       else
  253. #endif
  254.         may_overwrite = eaccess_stat (&to_stats, W_OK) == 0
  255.           && (to_stats.st_mode & 0222);
  256.  
  257.       if (!may_overwrite)
  258.         {
  259.           if (stdin_not_tty)
  260.         {
  261.           error (0, 0, "%s: no write permission", to);
  262.           return 1;
  263.         }
  264.           fprintf (stderr, "%s: override mode %04o for `%s'? ",
  265.                program_name, to_stats.st_mode & 0777, to);
  266.           if (!yesno ())
  267.         return 0;
  268.         }
  269.     }
  270.  
  271.       if (backup_type != none)
  272.     {
  273.       to_backup = find_backup_file_name (to);
  274.       if (to_backup == NULL)
  275.         error (1, 0, "virtual memory exhausted");
  276.       if (rename (to, to_backup))
  277.         {
  278.           if (errno != ENOENT)
  279.         {
  280.           error (0, errno, "cannot backup `%s'", to);
  281.           free (to_backup);
  282.           return 1;
  283.         }
  284.           else
  285.         {
  286.           free (to_backup);
  287.           to_backup = NULL;
  288.         }
  289.         }
  290.     }
  291.     }
  292.   else if (errno != ENOENT)
  293.     {
  294.       error (0, errno, "%s", to);
  295.       return 1;
  296.     }
  297.  
  298.   if (rename (from, to) == 0)
  299.     {
  300.       if (to_backup)
  301.     free (to_backup);
  302.       return 0;
  303.     }
  304.  
  305.   if (errno != EXDEV)
  306.     {
  307.       error (0, errno, "cannot move `%s' to `%s'", from, to);
  308.       goto un_backup;
  309.     }
  310.  
  311.   /* rename failed on cross-filesystem link.  Copy the file instead. */
  312.  
  313.   if (copy (from, to))
  314.     goto un_backup;
  315.   
  316.   if (to_backup)
  317.     free (to_backup);
  318.  
  319.   if (unlink (from))
  320.     {
  321.       error (0, errno, "cannot remove `%s'", from);
  322.       return 1;
  323.     }
  324.  
  325.   return 0;
  326.  
  327.  un_backup:
  328.   if (to_backup)
  329.     {
  330.       if (rename (to_backup, to))
  331.     error (0, errno, "cannot un-backup `%s'", to);
  332.       free (to_backup);
  333.     }
  334.   return 1;
  335. }
  336.  
  337. /* Copy file FROM onto file TO.
  338.    Return 1 if an error occurred, 0 if successful. */
  339. int
  340. copy (from, to)
  341.      char *from, *to;
  342. {
  343.   int ifd;
  344.   int ofd;
  345.   char buf[1024 * 8];
  346.   int len;            /* Number of bytes read into `buf'. */
  347.   
  348.   if ((from_stats.st_mode & S_IFMT) != S_IFREG)
  349.     {
  350.       error (0, 0, "cannot move `%s' across filesystems: Not a regular file",
  351.          from);
  352.       return 1;
  353.     }
  354.   
  355.   if (unlink (to) && errno != ENOENT)
  356.     {
  357.       error (0, errno, "cannot remove `%s'", to);
  358.       return 1;
  359.     }
  360.  
  361.   ifd = open (from, O_RDONLY, 0);
  362.   if (ifd < 0)
  363.     {
  364.       error (0, errno, "%s", from);
  365.       return 1;
  366.     }
  367.   ofd = open (to, O_WRONLY | O_CREAT | O_TRUNC, 0777);
  368.   if (ofd < 0)
  369.     {
  370.       error (0, errno, "%s", to);
  371.       close (ifd);
  372.       return 1;
  373.     }
  374.   if (
  375. #ifdef FCHMOD_MISSING
  376.       chmod (to, from_stats.st_mode & 0777)
  377. #else
  378.       fchmod (ofd, from_stats.st_mode & 0777)
  379. #endif
  380.       )
  381.       {
  382.     error (0, errno, "%s", to);
  383.     close (ifd);
  384.     close (ofd);
  385.     unlink (to);
  386.     return 1;
  387.       }
  388.   
  389.   while ((len = read (ifd, buf, sizeof (buf))) > 0)
  390.     {
  391.       int wrote = 0;
  392.       char *bp = buf;
  393.       
  394.       do
  395.     {
  396.       wrote = write (ofd, bp, len);
  397.       if (wrote < 0)
  398.         {
  399.           error (0, errno, "%s", to);
  400.           close (ifd);
  401.           close (ofd);
  402.           unlink (to);
  403.           return 1;
  404.         }
  405.       bp += wrote;
  406.       len -= wrote;
  407.     } while (len > 0);
  408.     }
  409.   if (len < 0)
  410.     {
  411.       error (0, errno, "%s", from);
  412.       close (ifd);
  413.       close (ofd);
  414.       unlink (to);
  415.       return 1;
  416.     }
  417.   close (ifd);
  418.   close (ofd);
  419.   
  420.   /* Try to copy the old file's modtime and access time.  */
  421.   {
  422.     struct utimbuf tv;
  423.  
  424.     tv.actime = from_stats.st_atime;
  425.     tv.modtime = from_stats.st_mtime;
  426.     if (utime (to, &tv))
  427.       {
  428.     error (0, errno, "%s", to);
  429.     return 1;
  430.       }
  431.   }
  432.   return 0;
  433. }
  434.  
  435. /* Read one line from standard input
  436.    and return nonzero if that line begins with y or Y,
  437.    otherwise return 0. */
  438. int
  439. yesno ()
  440. {
  441.   int c;
  442.   int rv;
  443.  
  444.   fflush (stderr);
  445.   c = getchar ();
  446.   rv = (c == 'y') || (c == 'Y');
  447.   while (c != EOF && c != '\n')
  448.     c = getchar ();
  449.  
  450.   return rv;
  451. }
  452.  
  453. /* Remove trailing slashes from PATH; they cause some system calls to fail. */
  454. void
  455. strip_trailing_slashes (path)
  456.      char *path;
  457. {
  458.   int last;
  459.  
  460.   last = strlen (path) - 1;
  461.   while (last > 0 && path[last] == '/')
  462.     path[last--] = '\0';
  463. }
  464.  
  465. void
  466. usage ()
  467. {
  468.   fprintf (stderr, "\
  469. Usage: %s [-bfiv] [-S backup-suffix] [-V {numbered,existing,simple}]\n\
  470.        [+backup] [+force] [+interactive] [+verbose] [+suffix backup-suffix]\n\
  471.        [+version-control {numbered,existing,simple}] source dest\n\
  472. \n\
  473.        %s [-bfiv] [-S backup-suffix] [-V {numbered,existing,simple}]\n\
  474.        [+backup] [+force] [+interactive] [+verbose] [+suffix backup-suffix]\n\
  475.        [+version-control {numbered,existing,simple}] source... directory\n",
  476.        program_name, program_name);
  477.   exit (1);
  478. }
  479.